<?php

namespace app\admin\library;

use think\Db;

/**
 * 通用排程算法引擎
 */
class ScheduleEngine
{
    // 当前激活的行业配置
    private $industryConfig = null;
    
    // 排程规则
    private $rules = [];
    
    public function __construct()
    {
        // 加载激活的行业配置
        $this->loadIndustryConfig();
        
        // 加载排程规则
        $this->loadScheduleRules();
    }
    
    /**
     * 加载行业配置
     */
    private function loadIndustryConfig()
    {
        $this->industryConfig = Db::name('scanwork_industry_config')
            ->where('is_active', 1)
            ->find();
    }
    
    /**
     * 加载排程规则
     */
    private function loadScheduleRules()
    {
        $where = ['status' => 'normal'];
        
        // 优先加载当前行业的规则
        if ($this->industryConfig) {
            $industryRules = Db::name('scanwork_schedule_rule')
                ->where($where)
                ->where('industry_code', $this->industryConfig['industry_code'])
                ->order('rule_weight', 'desc')
                ->select();
            
            $this->rules = array_merge($this->rules, $industryRules);
        }
        
        // 加载通用规则
        $generalRules = Db::name('scanwork_schedule_rule')
            ->where($where)
            ->where('industry_code', 'exp', 'IS NULL')
            ->order('rule_weight', 'desc')
            ->select();
        
        $this->rules = array_merge($this->rules, $generalRules);
    }
    
    /**
     * 执行排程计算
     * @param int $scheduleId 排程ID
     * @param string $algorithm 算法类型: priority=优先规则, capacity=有限产能, manual=手动调整
     * @return array
     */
    public function execute($scheduleId, $algorithm = 'priority')
    {
        $schedule = Db::name('scanwork_schedule')->where('id', $scheduleId)->find();
        if (!$schedule) {
            throw new \Exception('排程不存在');
        }
        
        // 获取待排程的任务
        $tasks = $this->getScheduleTasks($scheduleId);
        
        // 根据算法执行排程
        switch ($algorithm) {
            case 'priority':
                $result = $this->priorityBasedSchedule($tasks, $schedule);
                break;
            case 'capacity':
                $result = $this->capacityConstraintSchedule($tasks, $schedule);
                break;
            case 'cycle':
                $result = $this->cyclicSchedule($tasks, $schedule);
                break;
            default:
                throw new \Exception('不支持的算法类型');
        }
        
        // 保存排程结果
        $this->saveScheduleResult($scheduleId, $result);
        
        return $result;
    }
    
    /**
     * 算法1：优先规则排程（适用于离散制造）
     */
    private function priorityBasedSchedule($tasks, $schedule)
    {
        $result = [];
        
        // 1. 应用排序规则
        $tasks = $this->applyPriorityRules($tasks);
        
        // 2. 获取资源信息
        $resources = $this->getAvailableResources($schedule['start_date'], $schedule['end_date']);
        
        // 3. 逐个安排任务
        foreach ($tasks as $task) {
            // 查找可用资源
            $resource = $this->findAvailableResource($task, $resources);
            
            if ($resource) {
                // 计算开始和结束时间
                $startTime = $this->calculateStartTime($task, $resource);
                $endTime = $this->calculateEndTime($task, $resource, $startTime);
                
                // 更新任务时间
                $task['plan_start_time'] = $startTime;
                $task['plan_end_time'] = $endTime;
                $task['work_center_id'] = $resource['work_center_id'];
                $task['equipment_id'] = $resource['equipment_id'];
                $task['employee_id'] = $resource['employee_id'];
                
                // 更新资源占用
                $this->updateResourceOccupancy($resource, $startTime, $endTime);
                
                $result[] = $task;
            } else {
                // 资源不足，标记为待排程
                $task['status'] = 0;
                $task['remark'] = '资源不足';
                $result[] = $task;
            }
        }
        
        return $result;
    }
    
    /**
     * 算法2：有限产能排程（通用算法）
     */
    private function capacityConstraintSchedule($tasks, $schedule)
    {
        $result = [];
        
        // 1. 获取资源产能信息
        $capacityMap = $this->buildCapacityMap($schedule['start_date'], $schedule['end_date']);
        
        // 2. 按交货期排序
        usort($tasks, function($a, $b) {
            return $a['required_date'] - $b['required_date'];
        });
        
        // 3. 考虑产能约束安排任务
        foreach ($tasks as $task) {
            $process = Db::name('scanwork_process')->where('id', $task['process_id'])->find();
            
            if (!$process) {
                continue;
            }
            
            // 计算所需工时
            $requiredHours = ($task['quantity'] * $process['standard_time']) + ($process['setup_time'] ?: 0);
            
            // 查找有足够产能的资源
            $resource = $this->findCapacityResource($process, $requiredHours, $capacityMap);
            
            if ($resource) {
                $startTime = $resource['next_available_time'];
                $endTime = $startTime + ($requiredHours * 3600);
                
                $task['plan_start_time'] = $startTime;
                $task['plan_end_time'] = $endTime;
                $task['work_center_id'] = $resource['work_center_id'];
                $task['equipment_id'] = $resource['equipment_id'];
                
                // 更新产能占用
                $capacityMap[$resource['id']]['next_available_time'] = $endTime;
                $capacityMap[$resource['id']]['used_hours'] += $requiredHours;
                
                $result[] = $task;
            } else {
                $task['status'] = 0;
                $task['remark'] = '产能不足';
                $result[] = $task;
            }
        }
        
        return $result;
    }
    
    /**
     * 算法3：循环排程（适用于流程制造）
     */
    private function cyclicSchedule($tasks, $schedule)
    {
        $result = [];
        
        // 1. 按批次分组
        $batches = $this->groupTasksByBatch($tasks);
        
        // 2. 获取生产线信息
        $productionLines = $this->getProductionLines();
        
        // 3. 循环安排批次
        $currentTime = strtotime($schedule['start_date']);
        
        foreach ($batches as $batch) {
            // 选择合适的生产线
            $line = $this->selectProductionLine($batch, $productionLines);
            
            if ($line) {
                foreach ($batch as $task) {
                    $process = Db::name('scanwork_process')->where('id', $task['process_id'])->find();
                    $processTime = ($task['quantity'] * $process['standard_time']) * 3600;
                    
                    $task['plan_start_time'] = $currentTime;
                    $task['plan_end_time'] = $currentTime + $processTime;
                    $task['work_center_id'] = $line['id'];
                    
                    $currentTime = $task['plan_end_time'];
                    
                    $result[] = $task;
                }
                
                // 批次间隔时间（清洗、切换等）
                if ($line['changeover_time']) {
                    $currentTime += $line['changeover_time'] * 3600;
                }
            }
        }
        
        return $result;
    }
    
    /**
     * 应用优先级规则
     */
    private function applyPriorityRules($tasks)
    {
        $priorityRules = array_filter($this->rules, function($rule) {
            return $rule['rule_type'] == 'priority' && $rule['is_default'] == 1;
        });
        
        // 默认按交货期和紧急程度排序
        usort($tasks, function($a, $b) {
            // 紧急订单优先
            if ($a['priority'] != $b['priority']) {
                return $b['priority'] - $a['priority'];
            }
            // 交货期早的优先
            return $a['required_date'] - $b['required_date'];
        });
        
        return $tasks;
    }
    
    /**
     * 获取待排程任务
     */
    private function getScheduleTasks($scheduleId)
    {
        return Db::name('scanwork_schedule_task')
            ->where('schedule_id', $scheduleId)
            ->where('status', 'in', [0, 1]) // 待排程或排程中
            ->order('priority desc, required_date asc')
            ->select();
    }
    
    /**
     * 获取可用资源
     */
    private function getAvailableResources($startDate, $endDate)
    {
        $resources = [];
        
        // 设备资源
        $equipment = Db::name('scanwork_equipment')
            ->where('status', 1)
            ->select();
        
        foreach ($equipment as $eq) {
            $resources[] = [
                'type' => 'equipment',
                'id' => $eq['id'],
                'equipment_id' => $eq['id'],
                'equipment_name' => $eq['equipment_name'],
                'capacity' => $eq['capacity'] ?: 0,
                'available_hours' => $eq['available_hours_per_day'] ?: 8,
                'next_available_time' => strtotime($startDate),
                'occupied_slots' => []
            ];
        }
        
        return $resources;
    }
    
    /**
     * 查找可用资源
     */
    private function findAvailableResource($task, &$resources)
    {
        $process = Db::name('scanwork_process')->where('id', $task['process_id'])->find();
        
        if (!$process) {
            return null;
        }
        
        foreach ($resources as &$resource) {
            // 检查资源类型匹配
            if ($this->isResourceMatch($resource, $process)) {
                return $resource;
            }
        }
        
        return null;
    }
    
    /**
     * 检查资源是否匹配
     */
    private function isResourceMatch($resource, $process)
    {
        // 简单匹配逻辑，可根据行业扩展
        return true;
    }
    
    /**
     * 计算开始时间
     */
    private function calculateStartTime($task, $resource)
    {
        return $resource['next_available_time'];
    }
    
    /**
     * 计算结束时间
     */
    private function calculateEndTime($task, $resource, $startTime)
    {
        $process = Db::name('scanwork_process')->where('id', $task['process_id'])->find();
        
        $totalHours = ($task['quantity'] * $process['standard_time']) + ($process['setup_time'] ?: 0);
        
        return $startTime + ($totalHours * 3600);
    }
    
    /**
     * 更新资源占用
     */
    private function updateResourceOccupancy(&$resource, $startTime, $endTime)
    {
        $resource['occupied_slots'][] = [
            'start' => $startTime,
            'end' => $endTime
        ];
        $resource['next_available_time'] = $endTime;
    }
    
    /**
     * 构建产能地图
     */
    private function buildCapacityMap($startDate, $endDate)
    {
        $capacityMap = [];
        
        $equipment = Db::name('scanwork_equipment')
            ->where('status', 1)
            ->select();
        
        foreach ($equipment as $eq) {
            $capacityMap[$eq['id']] = [
                'id' => $eq['id'],
                'work_center_id' => null,
                'equipment_id' => $eq['id'],
                'total_hours' => $eq['available_hours_per_day'] ?: 8,
                'used_hours' => 0,
                'next_available_time' => strtotime($startDate)
            ];
        }
        
        return $capacityMap;
    }
    
    /**
     * 查找有产能的资源
     */
    private function findCapacityResource($process, $requiredHours, $capacityMap)
    {
        foreach ($capacityMap as $resource) {
            if ($resource['total_hours'] - $resource['used_hours'] >= $requiredHours) {
                return $resource;
            }
        }
        
        return null;
    }
    
    /**
     * 按批次分组任务
     */
    private function groupTasksByBatch($tasks)
    {
        $batches = [];
        
        foreach ($tasks as $task) {
            $batchKey = $task['batch_no'] ?: 'default';
            if (!isset($batches[$batchKey])) {
                $batches[$batchKey] = [];
            }
            $batches[$batchKey][] = $task;
        }
        
        return $batches;
    }
    
    /**
     * 获取生产线
     */
    private function getProductionLines()
    {
        return Db::name('scanwork_work_center')
            ->where('status', 1)
            ->select();
    }
    
    /**
     * 选择生产线
     */
    private function selectProductionLine($batch, $productionLines)
    {
        return !empty($productionLines) ? $productionLines[0] : null;
    }
    
    /**
     * 保存排程结果
     */
    private function saveScheduleResult($scheduleId, $result)
    {
        Db::startTrans();
        try {
            foreach ($result as $task) {
                Db::name('scanwork_schedule_task')
                    ->where('id', $task['id'])
                    ->update([
                        'plan_start_time' => $task['plan_start_time'],
                        'plan_end_time' => $task['plan_end_time'],
                        'work_center_id' => $task['work_center_id'] ?? null,
                        'equipment_id' => $task['equipment_id'] ?? null,
                        'employee_id' => $task['employee_id'] ?? null,
                        'status' => $task['status'] ?? 1,
                        'remark' => $task['remark'] ?? '',
                        'updatetime' => time()
                    ]);
            }
            
            // 更新排程状态
            Db::name('scanwork_schedule')
                ->where('id', $scheduleId)
                ->update([
                    'status' => 2, // 已排程
                    'updatetime' => time()
                ]);
            
            Db::commit();
        } catch (\Exception $e) {
            Db::rollback();
            throw $e;
        }
    }
}
